package org.someone.inventory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Stack;

import javax.script.ScriptException;

import org.someone.common.ComparatorForMapSort;
import org.someone.common.CustomMaths;
import org.someone.common.ListTools;

public class Inventory {
	
	//Inventory order type : in
	public static String INV_BILL_TYP_IN = "IN";
	//Inventory order type : out
	public static String INV_BILL_TYP_OUT = "OUT";
	
	//Inventory algorithm : first in first out 先进先出
	public static String INV_OUT_TYP_FIFO = "FIFO";
	//Inventory algorithm : last in first out 后进先出
	public static String INV_OUT_TYP_LIFO = "LIFO";
	//Inventory algorithm : Moving weighted average 移动加权平均
	public static String INV_OUT_TYP_MOVINGWEIGHTED = "MOVINGWEIGHTED";
	//Inventory algorithm : weighted average 加权平均
	public static String INV_OUT_TYP_WEIGHTED = "WEIGHTED";
	//Inventory algorithm : Specific identification 个别计价
	public static String INV_OUT_TYP_SPECIFIC = "SPECIFIC";
	
	//Inventory mode key : owner
	public static String INV_KEY_OWNER = "OWNER";
	//Inventory mode key : location
	public static String INV_KEY_LOCATION = "LOCATION";
	//Inventory mode key : goods type
	public static String INV_KEY_GOODSTYPE = "GOODSTYPE";
	//Inventory mode key : specifications
	public static String INV_KEY_SPECIFICATIONS = "SPECIFICATIONS";
	//Inventory mode key : quantity
	public static String INV_KEY_QUANTITY = "QUANTITY";
	//Inventory mode key : unit price
	public static String INV_KEY_UNITPRICE = "UNITPRICE";
	//Inventory mode key : amount
	public static String INV_KEY_AMOUNT = "AMOUNT";
	//Inventory mode key : time
	public static String INV_KEY_TIME = "TIME";
	//Inventory mode key : qnet
	public static String INV_KEY_Q = "Q";
	//Inventory mode key : S
	public static String INV_KEY_S = "S";
	//Inventory mode key : A
	public static String INV_KEY_A = "A";
	//Inventory mode key : Mt
	public static String INV_KEY_MT = "MT";
	//Inventory mode key : M
	public static String INV_KEY_M = "M";
	//Inventory mode key : V
	public static String INV_KEY_V = "V";
	//Inventory mode key : group key
	public static String INV_KEY_GROUPKEY = "GROUPKEY";
	//Inventory mode key : bill type
	public static String INV_KEY_BILLTYPE = "BILLTYPE";
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include beginning bills,  inbound bills, outbound bills
	 * @param outType		String 					 Inventory out Type, include first in first out, last in first out, moving weighted average, weighted average 
	 * @return				List<Map<String,String>> Inventory outbound bills
	 * @throws Exception	
	 */
	public List<Map<String,String>> outBound(List<Map<String,String>> bills, String outType) throws Exception{
		List<Map<String,String>> result = new ArrayList<Map<String,String>>();
		//first:classify
		List<String> classifylist = new ArrayList<String>();
		for(int i=0; i < bills.size(); i++){
			Map<String,String> bill = bills.get(i);
			String groupValue = bill.get(INV_KEY_OWNER) + "-" + 
					bill.get(INV_KEY_LOCATION) + "-" + 
					bill.get(INV_KEY_GOODSTYPE) + "-" + 
					bill.get(INV_KEY_SPECIFICATIONS);
			bill.put(INV_KEY_GROUPKEY, groupValue);
		}
		for(int i=0; i < bills.size(); i++){
			Map<String,String> bill = bills.get(i);
			if(!classifylist.contains(bill.get(INV_KEY_GROUPKEY))){
				classifylist.add(bill.get(INV_KEY_GROUPKEY));
			}
		}
		Iterator<String> it = classifylist.iterator();
		while(it.hasNext()){
			String groupValue = (String) it.next();
			String eval = INV_KEY_GROUPKEY + "==" + groupValue;
			List<Map<String,String>> tmplist = new ArrayList<Map<String,String>>();
			try {
				tmplist= ListTools.cloneListMap(ListTools.extract(eval, bills));
			} catch (ScriptException e) {
				throw new Exception("分组公式解析错误，请检查货主、货位、物种、规格信息。");
			}
			//second:sort
			ComparatorForMapSort c = new ComparatorForMapSort("time");
			Collections.sort(tmplist, c);
			//third:calculation
			if(outType.equalsIgnoreCase(INV_OUT_TYP_MOVINGWEIGHTED)){
				result.addAll(Calculation4MovingWeighted(tmplist));
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_WEIGHTED)){
				result.addAll(Calculation4Weighted(tmplist));
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_FIFO)){
				result.addAll(Calculation4FIFO(tmplist));
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_LIFO)){
				result.addAll(Calculation4LIFO(tmplist));
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include same owner, same location, same goodstype, same specifications and by sort
	 * @return				List<Map<String,String>> Inventory outbound bills
	 * @throws Exception	
	 */
	public List<Map<String,String>> Calculation4MovingWeighted(List<Map<String,String>> bills) throws Exception{
		double quantity = 0.0;
		double unitprice = 0.0;
		double amount = 0.0;
		double sumquantity = 0.0;
		double sumamount = 0.0;
		List<Map<String,String>> out = new ArrayList<Map<String,String>>();
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			quantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
			if(INV_BILL_TYP_IN.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){	
				unitprice = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
				amount = CustomMaths.round(quantity * unitprice,2);
				sumquantity += quantity;
				sumamount += amount;
			}else if(INV_BILL_TYP_OUT.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				sumquantity = sumquantity - quantity;
				if(sumquantity<=0){
					throw new Exception("库存结余为零无法继续出库。");
				}
				unitprice = CustomMaths.div(sumamount, sumquantity, 2);
				amount = CustomMaths.round(quantity * unitprice,2);
				sumamount = sumamount - amount;
				m.put(INV_KEY_UNITPRICE, String.valueOf(unitprice));
				m.put(INV_KEY_AMOUNT, String.valueOf(amount));
				out.add(m);
			}
		}
		return out;
	}
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include same owner, same location, same goodstype, same specifications and by sort
	 * @return				List<Map<String,String>> Inventory outbound bills, 
	 * @throws Exception	
	 */
	public List<Map<String,String>> Calculation4Weighted(List<Map<String,String>> bills) throws Exception{
		double quantity = 0.0;
		double unitprice = 0.0;
		double amount = 0.0;
		double sumquantity = 0.0;
		double sumamount = 0.0;
		List<Map<String,String>> out = new ArrayList<Map<String,String>>();
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			quantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
			if(INV_BILL_TYP_IN.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){	
				unitprice = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
				amount = CustomMaths.round(quantity * unitprice,2);
				sumquantity += quantity;
				sumamount += amount;
			}
		}
		unitprice = CustomMaths.div(sumamount, sumquantity, 2);
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			quantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
			if(INV_BILL_TYP_OUT.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				sumquantity = sumquantity - quantity;
				if(sumquantity<=0){
					throw new Exception("库存结余为零无法继续出库。");
				}
				amount = CustomMaths.round(quantity * unitprice,2);
				m.put(INV_KEY_UNITPRICE, String.valueOf(unitprice));
				m.put(INV_KEY_AMOUNT, String.valueOf(amount));
				out.add(m);
			}
		}
		return out;
	}
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include same owner, same location, same goodstype, same specifications and by sort
	 * @return				List<Map<String,String>> Inventory outbound bills, 
	 * @throws Exception	
	 */
	public List<Map<String,String>> Calculation4FIFO(List<Map<String,String>> bills) throws Exception{
		List<Map<String,String>> out = new ArrayList<Map<String,String>>();
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			Queue<Map<String,String>> queue = new LinkedList<Map<String,String>>();
			if(INV_BILL_TYP_IN.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){	
				queue.offer(m);
			}else
			if(INV_BILL_TYP_OUT.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				double InQuantity = 0.0;
				double InUnitprice = 0.0;
				double sumQuantity = 0.0;
				double sumAmount = 0.0;
				double Unitprice = 0.0;
				double OutQuantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
				sumQuantity = OutQuantity;
				while(OutQuantity>0){
					Map<String,String> im = queue.peek();
					if(im==null){
						throw new Exception("库存结余为零无法出库。");
					}
					InQuantity = CustomMaths.round(Double.parseDouble(im.get(INV_KEY_QUANTITY)),2);
					InUnitprice = CustomMaths.round(Double.parseDouble(im.get(INV_KEY_UNITPRICE)),2);
					if(InQuantity>0){
						if(InQuantity > OutQuantity){
							//计算总金额
							sumAmount += CustomMaths.round(OutQuantity * InUnitprice,2);
							//计算出库量
							OutQuantity = 0;
							//更新队列头的剩余量
							InQuantity = InQuantity - OutQuantity;
							im.put(INV_KEY_QUANTITY, String.valueOf(InQuantity));
						}else{
							//计算总金额
							sumAmount += CustomMaths.round(InQuantity * InUnitprice,2);
							//计算出库量
							OutQuantity = OutQuantity - InQuantity;
							//出列
							queue.poll();
						}
					}
				};
				Unitprice =  CustomMaths.div(sumAmount, sumQuantity, 2);
				m.put(INV_KEY_UNITPRICE, String.valueOf(Unitprice));
				m.put(INV_KEY_AMOUNT, String.valueOf(sumAmount));
				out.add(m);
			}
		}
		return out;
	}
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include same owner, same location, same goodstype, same specifications and by sort
	 * @return				List<Map<String,String>> Inventory outbound bills, 
	 * @throws Exception	
	 */
	public List<Map<String,String>> Calculation4LIFO(List<Map<String,String>> bills) throws Exception{
		List<Map<String,String>> out = new ArrayList<Map<String,String>>();
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			Stack<Map<String,String>> stack = new Stack<Map<String,String>>();
			if(INV_BILL_TYP_IN.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){	
				stack.push(m);
			}else
			if(INV_BILL_TYP_OUT.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				double InQuantity = 0.0;
				double InUnitprice = 0.0;
				double sumQuantity = 0.0;
				double sumAmount = 0.0;
				double Unitprice = 0.0;
				double OutQuantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
				sumQuantity = OutQuantity;
				while(OutQuantity>0){
					Map<String,String> im = stack.peek();
					if(im==null){
						throw new Exception("库存结余为零无法出库。");
					}
					InQuantity = CustomMaths.round(Double.parseDouble(im.get(INV_KEY_QUANTITY)),2);
					InUnitprice = CustomMaths.round(Double.parseDouble(im.get(INV_KEY_UNITPRICE)),2);
					if(InQuantity>0){
						if(InQuantity > OutQuantity){
							//计算总金额
							sumAmount += CustomMaths.round(OutQuantity * InUnitprice,2);
							//计算出库量
							OutQuantity = 0;
							//更新队列头的剩余量
							InQuantity = InQuantity - OutQuantity;
							im.put(INV_KEY_QUANTITY, String.valueOf(InQuantity));
						}else{
							//计算总金额
							sumAmount += CustomMaths.round(InQuantity * InUnitprice,2);
							//计算出库量
							OutQuantity = OutQuantity - InQuantity;
							//出列
							stack.pop();
						}
					}
				};
				Unitprice =  CustomMaths.div(sumAmount, sumQuantity, 2);
				m.put(INV_KEY_UNITPRICE, String.valueOf(Unitprice));
				m.put(INV_KEY_AMOUNT, String.valueOf(sumAmount));
				out.add(m);
			}
		}
		return out;
	}
	
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include beginning bills, inbound bills, outbound bills
	 * @param outType		String 					 Inventory out Type, include first in first out, last in first out, moving weighted average, weighted average 
	 * @return				List<Map<String,String>> Inventory outbound bills
	 * @throws Exception	
	 */
	public List<Map<String,String>> CalINVBalance(List<Map<String,String>> bills, String outType) throws Exception{
		List<Map<String,String>> result = new ArrayList<Map<String,String>>();
		//first:classify
		List<String> classifylist = new ArrayList<String>();
		for(int i=0; i < bills.size(); i++){
			Map<String,String> bill = bills.get(i);
			String groupValue = bill.get(INV_KEY_OWNER) + "-" + 
					bill.get(INV_KEY_LOCATION) + "-" + 
					bill.get(INV_KEY_GOODSTYPE) + "-" + 
					bill.get(INV_KEY_SPECIFICATIONS);
			bill.put(INV_KEY_GROUPKEY, groupValue);
		}
		for(int i=0; i < bills.size(); i++){
			Map<String,String> bill = bills.get(i);
			if(!classifylist.contains(bill.get(INV_KEY_GROUPKEY))){
				classifylist.add(bill.get(INV_KEY_GROUPKEY));
			}
		}
		Iterator<String> it = classifylist.iterator();
		while(it.hasNext()){
			String groupValue = (String) it.next();
			String eval = INV_KEY_GROUPKEY + "==" + groupValue;
			List<Map<String,String>> tmplist = new ArrayList<Map<String,String>>();
			try {
				tmplist= ListTools.cloneListMap(ListTools.extract(eval, bills));
			} catch (ScriptException e) {
				throw new Exception("分组公式解析错误，请检查货主、货位、物种、规格信息。");
			}
			//second:sort
			ComparatorForMapSort c = new ComparatorForMapSort("time");
			Collections.sort(tmplist, c);
			//third:calculation
			if(outType.equalsIgnoreCase(INV_OUT_TYP_MOVINGWEIGHTED)){
				result.add(Bal4MovingWeighted(tmplist));
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_WEIGHTED)){
				
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_FIFO)){
				
			}else if(outType.equalsIgnoreCase(INV_OUT_TYP_LIFO)){
				
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param bills			List<Map<String,String>> Inventory bills, include same owner, same location, same goodstype, same specifications and by sort
	 * @return				List<Map<String,String>> Inventory outbound bills
	 * @throws Exception	
	 */
	public Map<String,String> Bal4MovingWeighted(List<Map<String,String>> bills) throws Exception{
		double quantity = 0.0;
		double unitprice = 0.0;
		double amount = 0.0;
		double sumquantity = 0.0;
		double sumamount = 0.0;
		double Qnettmp = 0.0;
		double Stmp = 0.0;
		double Vtmp = 0.0;
		double Atmp = 0.0;
		double Mttmp = 0.0;
		double Mtmp = 0.0;
		double Qnet = 0.0;
		double S = 0.0;
		double V = 0.0;
		double A = 0.0;
		double Mt = 0.0;
		double M = 0.0;
		Map<String,String> out = new HashMap<String,String>();
		for(int i=0; i< bills.size(); i++){
			Map<String,String> m = bills.get(i);
			if(i==0){
				out.putAll(m);
			}
			quantity = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_QUANTITY)),2);
			if(INV_BILL_TYP_IN.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				unitprice = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_UNITPRICE)==null?"0":m.get(INV_KEY_UNITPRICE)),2);
				Qnettmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_Q)==null?"0":m.get(INV_KEY_Q)),2);
				Stmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_S)==null?"0":m.get(INV_KEY_S)),2);
				Vtmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_V)==null?"0":m.get(INV_KEY_V)),2);
				Atmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_A)==null?"0":m.get(INV_KEY_A)),2);
				Mttmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_MT)==null?"0":m.get(INV_KEY_MT)),2);
				Mtmp = CustomMaths.round(Double.parseDouble(m.get(INV_KEY_M)==null?"0":m.get(INV_KEY_M)),2);
				if(CustomMaths.add(quantity, sumquantity) > 0){
					Qnet = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Qnettmp),CustomMaths.mul(sumquantity, Qnet)),CustomMaths.add(quantity, sumquantity),2) ;
					S = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Stmp),CustomMaths.mul(sumquantity, S)),CustomMaths.add(quantity, sumquantity),2) ;
					V = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Vtmp),CustomMaths.mul(sumquantity, V)),CustomMaths.add(quantity, sumquantity),2) ;
					A = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Atmp),CustomMaths.mul(sumquantity, A)),CustomMaths.add(quantity, sumquantity),2) ;
					Mt = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Mttmp),CustomMaths.mul(sumquantity, Mt)),CustomMaths.add(quantity, sumquantity),2) ;
					M = CustomMaths.div(CustomMaths.add(CustomMaths.mul(quantity, Mtmp),CustomMaths.mul(sumquantity, M)),CustomMaths.add(quantity, sumquantity),2) ;
				}
				amount = CustomMaths.round(quantity * unitprice,2);
				sumquantity += quantity;
				sumamount += amount;
			}else if(INV_BILL_TYP_OUT.equalsIgnoreCase(m.get(INV_KEY_BILLTYPE))){
				unitprice = CustomMaths.div(sumamount, sumquantity, 2);
				sumquantity = sumquantity - quantity;
				if(sumquantity<=0){
					throw new Exception("库存结余为零无法继续出库。");
				}
				amount = CustomMaths.round(quantity * unitprice,2);
				sumamount = sumamount - amount;
			}
		}
		out.put(INV_KEY_QUANTITY, String.valueOf(sumquantity));
		unitprice = CustomMaths.div(sumamount, sumquantity, 2);
		out.put(INV_KEY_UNITPRICE, String.valueOf(unitprice));
		out.put(INV_KEY_AMOUNT, String.valueOf(sumamount));
		out.put(INV_KEY_Q, String.valueOf(Qnet));
		out.put(INV_KEY_S, String.valueOf(S));
		out.put(INV_KEY_V, String.valueOf(V));
		out.put(INV_KEY_A, String.valueOf(A));
		out.put(INV_KEY_MT, String.valueOf(Mt));
		out.put(INV_KEY_M, String.valueOf(M));
		return out;
	}
}
